﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using HP.Cachalote.WPF.Model;
using HP.Cachalote.WPF.ExtendedAttachedProperties;
using HP.Cachalote.Services.Interfaces;
using HP.Cachalote.DataModels;
using HP.Cachalote.WPF.Shared;

namespace HP.Cachalote.WPF.ViewModel
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class TodoLabelTreeViewItemViewModel : MyViewModelBase, ITextBoxController
    {
        #region Members of ITextBoxController
        public event TextBoxControllerEventHandler SetFocus;
        public event TextBoxControllerEventHandler MoveFocusToNext;
        public event TextBoxControllerEventHandler MoveCursorToBeginning;
        public bool SetFocusInstantly { get; set; }
        #endregion

        public const string DeleteLabelConfirmMessageToken = "DeleteLabelConfirm";
        public const string SelectToShowRelatedTodoItemsMessageToken = "SelectToShowRelatedTodoItems";

        public int ID { get; set; }
        public int OrderIndex { get; set; }
        public bool InitializedSecondLevelSubLabels { get; set; }
        public TodoLabelTreeViewItemViewModel Parent { get; set; }

        // Setup this property to support root item. It stores all same level TodoLabels. 
        // Then when Add Label command is invoked the new TodoLabel will be added to PeerLabels instead of SubLabels.
        public ObservableCollection<TodoLabelTreeViewItemViewModel> PeerLabels { get; set; }

        private ITodoLabelService _todoLabelService;
        private string _preChangedName;

        private enum TodoLabelItemPosition
        {
            Above,
            Below,
            Child
        }

        /// <summary>
        /// Initializes a new instance of the TodoLabelTreeViewItemViewModel class.
        /// </summary>
        public TodoLabelTreeViewItemViewModel(IDataService dataService)
            : base(dataService)
        {
            InitilaizeComponents(dataService);
            RegisterTodoItemsChangedNotification();
        }

        /// <summary>
        /// Load related TodoItem count. Because user can expand the node without clicking on the node.
        /// The related TodoItem list is not loaded yet. That's why need to load the count separately.
        /// </summary>
        public void LoadRelatedTodoItemCount()
        {
            RelatedTodoItemCount = ServiceFactory.CreateTodoItemService().GetRowCount(item => item.Label.ID == ID && !item.IsDone);
        }

        /// <summary>
        /// The <see cref="Name" /> property's name.
        /// </summary>
        public const string NamePropertyName = "Name";

        private string _name = string.Empty;

        /// <summary>
        /// Sets and gets the Name property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                if (_name == value)
                {
                    return;
                }

                RaisePropertyChanging(NamePropertyName);
                _name = value;
                RaisePropertyChanged(NamePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="RelatedTodoItemCount" /> property's name.
        /// </summary>
        public const string RelatedTodoItemCountPropertyName = "RelatedTodoItemCount";

        private int _relatedTodoItemCount = 0;

        /// <summary>
        /// Sets and gets the RelatedTodoItemCount property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public int RelatedTodoItemCount
        {
            get
            {
                return _relatedTodoItemCount;
            }

            set
            {
                if (_relatedTodoItemCount == value)
                {
                    return;
                }

                RaisePropertyChanging(RelatedTodoItemCountPropertyName);
                _relatedTodoItemCount = value;
                RaisePropertyChanged(RelatedTodoItemCountPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="IsExpanded" /> property's name.
        /// </summary>
        public const string IsExpandedPropertyName = "IsExpanded";

        private bool _isExpanded = false;

        /// <summary>
        /// Sets and gets the IsExpanded property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsExpanded
        {
            get
            {
                return _isExpanded;
            }

            set
            {
                if (_isExpanded == value)
                {
                    return;
                }

                RaisePropertyChanging(IsExpandedPropertyName);
                _isExpanded = value;
                RaisePropertyChanged(IsExpandedPropertyName);

                // Load sub-labels on demand.
                if (!InitializedSecondLevelSubLabels)
                {
                    try
                    {
                        LoadSecondLevelSubLabels();
                        InitializedSecondLevelSubLabels = true;
                    }
                    catch (System.Exception ex)
                    {
                        RequestDisplayErrorMessage(ex.Message);
                    }
                }
            }
        }

        /// <summary>
        /// The <see cref="TodoLabelNameVisibility" /> property's name.
        /// </summary>
        public const string TodoLabelNameVisibilityPropertyName = "TodoLabelNameVisibility";

        private Visibility _todoLabelNameVisibility = Visibility.Visible;

        /// <summary>
        /// Sets and gets the TodoLabelNameVisibility property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public Visibility TodoLabelNameVisibility
        {
            get
            {
                return _todoLabelNameVisibility;
            }

            set
            {
                if (_todoLabelNameVisibility == value)
                {
                    return;
                }

                RaisePropertyChanging(TodoLabelNameVisibilityPropertyName);
                _todoLabelNameVisibility = value;
                RaisePropertyChanged(TodoLabelNameVisibilityPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="TodoLabelNameInputVisibility" /> property's name.
        /// </summary>
        public const string TodoLabelNameInputVisibilityPropertyName = "TodoLabelNameInputVisibility";

        private Visibility _todoLabelNameInputVisibility = Visibility.Hidden;

        /// <summary>
        /// Sets and gets the TodoLabelNameInputVisibility property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public Visibility TodoLabelNameInputVisibility
        {
            get
            {
                return _todoLabelNameInputVisibility;
            }

            set
            {
                if (_todoLabelNameInputVisibility == value)
                {
                    return;
                }

                RaisePropertyChanging(TodoLabelNameInputVisibilityPropertyName);
                _todoLabelNameInputVisibility = value;
                RaisePropertyChanged(TodoLabelNameInputVisibilityPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="SubLabels" /> property's name.
        /// </summary>
        public const string SubLabelsPropertyName = "SubLabels";

        private ObservableCollection<TodoLabelTreeViewItemViewModel> _subLabels = 
            new ObservableCollection<TodoLabelTreeViewItemViewModel>();

        /// <summary>
        /// Sets and gets the SubLabels property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ObservableCollection<TodoLabelTreeViewItemViewModel> SubLabels
        {
            get
            {
                return _subLabels;
            }

            set
            {
                if (_subLabels == value)
                {
                    return;
                }

                RaisePropertyChanging(SubLabelsPropertyName);
                _subLabels = value;
                RaisePropertyChanged(SubLabelsPropertyName);
            }
        }

        private RelayCommand _clickCommand;

        /// <summary>
        /// Gets the ClickCommand.
        /// </summary>
        public RelayCommand ClickCommand
        {
            get
            {
                return _clickCommand
                    ?? (_clickCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to UnfinishedTodoItemTreeViewModel & FinishedTodoItemTreeViewModel 
                                              // to show the TodoItems based on selected TodoLabel or TodoItemFilter.

                                              SelectedLabelChangedNotification(ID, Name);
                                          }));
            }
        }

        private RelayCommand _editCommand;

        /// <summary>
        /// Gets the EditCommand.
        /// </summary>
        public RelayCommand EditCommand
        {
            get
            {
                return _editCommand
                    ?? (_editCommand = new RelayCommand(
                                          () =>
                                          {
                                              _preChangedName = Name;
                                              EnableEditingMode();

                                              if (SetFocus != null)
                                              {
                                                  SetFocus(this);
                                              }
                                          }));
            }
        }

        private RelayCommand<KeyEventArgs> _txtNameOnKeyDownCommand;

        /// <summary>
        /// Gets the TxtNameOnKeyDownCommand.
        /// </summary>
        public RelayCommand<KeyEventArgs> TxtNameOnKeyDownCommand
        {
            get
            {
                return _txtNameOnKeyDownCommand
                    ?? (_txtNameOnKeyDownCommand = new RelayCommand<KeyEventArgs>(ExecuteTxtNameOnKeyDownCommand));
            }
        }

        private void ExecuteTxtNameOnKeyDownCommand(KeyEventArgs e)
        {
            try
            {
                if (e.Key == Key.Return)
                {
                    // This will trigger LostFocus command and save the change.
                    DisableEditingMode();
                    SelectedLabelChangedNotification(ID, Name);
                }
                else if (e.Key == Key.Escape)
                {
                    // If user presses Escape key then discard the change.
                    Name = _preChangedName;
                    DisableEditingMode();
                    SelectedLabelChangedNotification(ID, Name);
                }
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        private RelayCommand _textBoxNameLostFocusCommand;

        /// <summary>
        /// Gets the TextBoxNameLostFocusCommand.
        /// </summary>
        public RelayCommand TextBoxNameLostFocusCommand
        {
            get
            {
                return _textBoxNameLostFocusCommand
                    ?? (_textBoxNameLostFocusCommand = new RelayCommand(ExecuteTextBoxNameLostFocusCommand));
            }
        }

        private void ExecuteTextBoxNameLostFocusCommand()
        {
            try
            {
                if (Name == null)
                {
                    Name = "New Label";
                }

                Name = Name.Trim();

                // If PreChangeName is not null and new Name is null then the value will be restored.
                if (string.IsNullOrEmpty(Name.Trim()) && !string.IsNullOrEmpty(_preChangedName))
                {
                    Name = _preChangedName;
                }
                else
                {
                    if (ID > 0)
                    {
                        // If ID > 0 means the data is existing and go to update the TodoLabel name.
                        UpdateLabel();
                    }
                    else
                    {
                        // If ID == 0 means the data is NOT existing and go to insert a new one.
                        InsertLabel();
                    }
                }

                DisableEditingMode();
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        private RelayCommand _addLabelAboveCommand;

        /// <summary>
        /// Gets the AddLabelAbove.
        /// </summary>
        public RelayCommand AddLabelAboveCommand
        {
            get
            {
                return _addLabelAboveCommand
                    ?? (_addLabelAboveCommand = new RelayCommand(
                        () =>
                        {
                            try
                            {
                                InsertLabelTreeViewItem(TodoLabelItemPosition.Above);
                            }
                            catch (System.Exception ex)
                            {
                                RequestDisplayErrorMessage(ex.Message);
                            }
                        }));
            }
        }

        private RelayCommand _addLabelBelowCommand;

        /// <summary>
        /// Gets the AddLabelBelow.
        /// </summary>
        public RelayCommand AddLabelBelowCommand
        {
            get
            {
                return _addLabelBelowCommand
                    ?? (_addLabelBelowCommand = new RelayCommand(() =>
                        {
                            try
                            {
                                InsertLabelTreeViewItem(TodoLabelItemPosition.Below);
                            }
                            catch (System.Exception ex)
                            {
                                RequestDisplayErrorMessage(ex.Message);
                            }
                        }));
            }
        }

        private RelayCommand _addChildLabelCommand;

        /// <summary>
        /// Gets the AddChildLabelCommand.
        /// </summary>
        public RelayCommand AddChildLabelCommand
        {
            get
            {
                return _addChildLabelCommand
                    ?? (_addChildLabelCommand = new RelayCommand(() =>
                    {
                        try
                        {
                            InsertLabelTreeViewItem(TodoLabelItemPosition.Child);
                        }
                        catch (System.Exception ex)
                        {
                            RequestDisplayErrorMessage(ex.Message);
                        }
                    }));
            }
        }

        private RelayCommand _deleteLabelCommand;

        /// <summary>
        /// Gets the DeleteLabelCommand.
        /// </summary>
        public RelayCommand DeleteLabelCommand
        {
            get
            {
                return _deleteLabelCommand
                    ?? (_deleteLabelCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Show a message to confirm whether user needs to delete the selected label.
                                              var dialogMessage = new DialogMessage("Delete selected label?", DeleteSelectedLabel)
                                              {
                                                  Button = MessageBoxButton.YesNo,
                                                  Caption = "Delete Label",
                                                  Icon = MessageBoxImage.Question
                                              };

                                              // Send message to TodoLabelTreeViewUserControl to show a deletion confirm dialog.
                                              Messenger.Default.Send<DialogMessage>(dialogMessage, DeleteLabelConfirmMessageToken);
                                          },
                                          () =>
                                          {
                                              if (Parent == null && PeerLabels.Count == 0)
                                              {
                                                  // If there only one root item remains then user cannot delete it. 
                                                  // Otherwise there is no way to select Add Label Command.
                                                  return false;
                                              }
                                              else if (SubLabels.Count > 0)
                                              {
                                                  // If current label contains sub-labels then it cannot be deleted.
                                                  return false;
                                              }
                                              else
                                              {
                                                  return true;
                                              }
                                          }));
            }
        }

        private void DeleteSelectedLabel(MessageBoxResult result)
        {
            try
            {
                if (result == MessageBoxResult.Yes)
                {
                    RemoveLabelTreeViewItem();
                    DeleteLabelFromDataSource();
                    SelectTheOtherLabel();
                }
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        /// <summary>
        /// Load second level sub-labels. That means load each sub-labels' sub-labels.
        /// </summary>
        private void LoadSecondLevelSubLabels()
        {
            foreach (var secondSubLabel in SubLabels)
            {
                var subLabels = (from label in _todoLabelService.Filter(label => label.Parent.ID == secondSubLabel.ID).OrderBy(label => label.OrderIndex)
                                 select new TodoLabelTreeViewItemViewModel(CurrentDataService)
                                            {
                                                ID = label.ID,
                                                Parent = secondSubLabel,
                                                Name = label.Name,
                                                OrderIndex = label.OrderIndex
                                            });

                secondSubLabel.SubLabels = new ObservableCollection<TodoLabelTreeViewItemViewModel>(subLabels);

                foreach (var item in secondSubLabel.SubLabels)
                {
                    item.PeerLabels = secondSubLabel.SubLabels;
                    item.LoadRelatedTodoItemCount();
                }
            }
        }

        /// <summary>
        /// Update TodoLabel in database.
        /// </summary>
        private void UpdateLabel()
        {
            if (_preChangedName != Name)
            {
                var todoLabel = _todoLabelService.Get(ID);
                todoLabel.Name = Name;

                _todoLabelService.Update(todoLabel);
            }
        }

        /// <summary>
        /// Insert a new TodoLabel into database.
        /// </summary>
        private void InsertLabel()
        {
            // Insert TodoLabel into database
            var label = new TodoLabel
            {
                Name = Name,
                OrderIndex = OrderIndex,
                Parent = this.Parent == null ? null : new TodoLabel { ID = Parent.ID }
            };

            ID = (int)_todoLabelService.Insert(label);
        }

        /// <summary>
        /// Insert a new TreeViewItem to the specified position based on current item's order index.
        /// </summary>
        /// <param name="itemPosition">TodoLabelItemPosition, Above current item, Below current item or Child of current item.</param>
        private void InsertLabelTreeViewItem(TodoLabelItemPosition itemPosition)
        {
            int newOrderIndex = 0;
            
            // Calcuate the new order index
            switch (itemPosition)
            {
                case TodoLabelItemPosition.Above:
                    newOrderIndex = OrderIndex;
                    break;
                case TodoLabelItemPosition.Below:
                    newOrderIndex = OrderIndex + 1;
                    break;
                case TodoLabelItemPosition.Child:
                    newOrderIndex = 0;
                    break;
            }

            // Create a new instance of TodoLabelTreeViewItemViewModel
            var newLabelItem = new TodoLabelTreeViewItemViewModel(CurrentDataService)
            {
                Name = "New Label",
                SetFocusInstantly = true,                        // Set this property to make the TextBox be focused once it is bound.
                TodoLabelNameVisibility = Visibility.Hidden,         // Hide the TextBlock to enable editing mode.
                TodoLabelNameInputVisibility = Visibility.Visible,          // Show the TextBox to enable editing mode.
                OrderIndex = newOrderIndex,

                // Set this property to make root item support Add Label function since root item does not have Parent to access.
                PeerLabels = itemPosition == TodoLabelItemPosition.Child ? this.SubLabels : this.PeerLabels,

                // If is adding a child item then set the Parent to current item, otherwise they share the same Parent.
                Parent = itemPosition == TodoLabelItemPosition.Child ? this : this.Parent
            };

            // This TodoLabel collection is used to accpet new added TodoLabel.
            ObservableCollection<TodoLabelTreeViewItemViewModel> labels;

            if (itemPosition == TodoLabelItemPosition.Child)
            {
                // If user adds a child TodoLabel then the collection will be the same as current item's SubLabels.
                labels = SubLabels;

                // Current item needs to be expanded if a child item is added.
                IsExpanded = true;
            }
            else
            {
                labels = PeerLabels;
            }

            // Insert the new added TodoLabelTreeViewItemViewModel to the tree.
            labels.Insert(newOrderIndex, newLabelItem);

            // Reset the other TodoLabelTreeViewItemViewModel items' OrderIndex.
            for (int i = newOrderIndex + 1; i < labels.Count; i++)
            {
                labels[i].OrderIndex += 1;
            }
        }

        /// <summary>
        /// Remove selected TreeViewItem from TreeView.
        /// </summary>
        private void RemoveLabelTreeViewItem()
        {
            var labels = PeerLabels;

            // Reset the other TodoLabelTreeViewItemViewModel items' OrderIndex.
            for (int i = OrderIndex + 1; i < labels.Count; i++)
            {
                labels[i].OrderIndex -= 1;
            }

            // Remove current TodoLabelTreeViewItemViewModel from the tree.
            labels.Remove(this);
        }

        /// <summary>
        /// Delete the TodoLabel from data source.
        /// </summary>
        private void DeleteLabelFromDataSource()
        {
            // Delete the label from database.
            var label = new TodoLabel
            {
                ID = this.ID,
                Parent = this.Parent == null ? null : new TodoLabel { ID = this.Parent.ID },
                OrderIndex = this.OrderIndex
            };

            _todoLabelService.Delete(label);
        }

        /// <summary>
        /// Select the other label to show the related items.
        /// </summary>
        private void SelectTheOtherLabel()
        {
            if (Parent != null)
            {
                // Set the parent as selected.
                SelectedLabelChangedNotification(Parent.ID, Parent.Name);
            }
            else
            {
                // Set the first peer label as selected.
                SelectedLabelChangedNotification(PeerLabels[0].ID, PeerLabels[0].Name);
            }
        }

        /// <summary>
        /// Enable the editing mode. Show the textbox and hide the textblock.
        /// </summary>
        private void EnableEditingMode()
        {
            TodoLabelNameVisibility = Visibility.Hidden;
            TodoLabelNameInputVisibility = Visibility.Visible;
        }

        /// <summary>
        /// Disable the editing mode. Hide the textbox and show the textblock.
        /// </summary>
        private void DisableEditingMode()
        {
            TodoLabelNameVisibility = Visibility.Visible;
            TodoLabelNameInputVisibility = Visibility.Hidden;
        }

        /// <summary>
        /// Initialize all file
        /// </summary>
        /// <param name="dataService"></param>
        private void InitilaizeComponents(IDataService dataService)
        {
            InitializedSecondLevelSubLabels = false;
            SetFocusInstantly = false;
            SubLabels = new ObservableCollection<TodoLabelTreeViewItemViewModel>();
            PeerLabels = new ObservableCollection<TodoLabelTreeViewItemViewModel>();

            _todoLabelService = ServiceFactory.CreateLabelService();
        }

        /// <summary>
        /// Get the message from UnfinishedTodoItemTreeViewModel to be observer of TodoItems change notification receiver.
        /// </summary>
        private void RegisterTodoItemsChangedNotification()
        {
            Messenger.Default.Register<UnfinishedTodoItemTreeViewModel>(this, UnfinishedTodoItemTreeViewModel.RelatedTodoItemsChangedMessageToken, itemTreeViewModel =>
            {
                if (itemTreeViewModel.CurrentTodoLabel != null && itemTreeViewModel.CurrentTodoLabel.ID == ID)
                {
                    if (itemTreeViewModel.ActionCategory == UnfinishedTodoItemTreeViewModel.TodoItemActionCategory.Done)
                    {
                        RelatedTodoItemCount -= 1;
                    }
                    else if (itemTreeViewModel.ActionCategory == UnfinishedTodoItemTreeViewModel.TodoItemActionCategory.Undone)
                    {
                        RelatedTodoItemCount += 1;
                    }
                }
            });
        }

        /// <summary>
        /// Selected Label Changed Notification
        /// </summary>
        private void SelectedLabelChangedNotification(int labelID, string labelName)
        {
            var todoLabel = new TodoLabel
            {
                ID = labelID,
                Name = labelName
            };

            Messenger.Default.Send<object>(todoLabel, SelectToShowRelatedTodoItemsMessageToken);
        }
    }
}